package com.ruoyi.common.cos.service;

import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.utils.Jackson;
import com.ruoyi.common.exception.ServiceException;
import com.tencent.cloud.CosStsClient;
import com.tencent.cloud.Policy;
import com.tencent.cloud.Response;
import com.tencent.cloud.Statement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;

@Service
public class CosService {

    @Autowired
    private COSClient cosClient;


    @Value("${cos.appId}")
    private String appId;

    @Value("${cos.bucketName}")
    private String bucketName;

    @Value("${cos.secretId}")
    private String secretId;

    @Value("${cos.secretKey}")
    private String secretKey;

    @Value("${cos.url}")
    private String url;


    @Value("${cos.region}")
    private String region;


    public Map<String, String> uploadFile(File file, String key) {
        // 上传文件
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, file);
        cosClient.putObject(putObjectRequest);

        // 获取文件的完整路径
        String completeUrl = cosClient.getObjectUrl(bucketName, key).toString();

        // 提取 "yafucui-1343947476.ap-guangzhou.cos.myqcloud.com" 后面的部分
        // 从 URL 中去除前缀 "https://yafucui-1343947476.cos.ap-nanjing.myqcloud.com/"
        String fileName = completeUrl.replace(url, "");
        // 创建返回的 Map
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("url", completeUrl);
        resultMap.put("fileName", fileName);

        // 返回 Map
        return resultMap;
    }

    /**
     * 通过 URL 上传文件到腾讯云 COS
     * @param fileUrl 文件的 URL 地址
     * @param key 文件在 COS 上的存储路径（文件名）
     * @return 文件在 COS 上的访问 URL
     * @throws IOException
     */
    public String uploadFileFromUrl(String fileUrl, String key) throws IOException {
        // 将 URL 转换为输入流
        URL url = new URL(fileUrl);
        try (InputStream inputStream = url.openStream()) {
            // 使用 COS SDK 上传文件
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, inputStream, null);
            PutObjectResult result = cosClient.putObject(putObjectRequest);

            // 返回文件的访问 URL
            return cosClient.getObjectUrl(bucketName, key).toString();
        }
    }

    /**
     * 下载文件
     * @param key COS 上的文件名
     * @return COSObject 文件对象
     */
    public COSObject downloadFile(String key) {
        // 从 COS 下载文件
        return cosClient.getObject(bucketName, key);
    }
    /**
     * 通过文件路径从 COS 获取文本内容
     * @param key COS 上的文件路径
     * @return 文件内容的文本
     * @throws IOException
     */
    public String getTextFromFile(String key) {
        try {
            // 获取 COS 上的文件对象
            COSObject cosObject = cosClient.getObject(bucketName, key);

            // 从 COSObject 获取输入流
            try (InputStream inputStream = cosObject.getObjectContent();
                 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {

                StringBuilder content = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    content.append(line).append("\n");
                }

                // 返回文件内容
                return content.toString();
            }
        } catch (Exception e) {
            throw new ServiceException("获取文本内容失败：" + e.getMessage());
        }
    }


    /**
     * 分块上传的具体实现
     * @param file 上传的文件块
     * @param uploadId 唯一标识每次上传
     * @param chunkIndex 当前块的索引
     * @param totalChunks 总块数
     * @param key 上传文件的路径
     * @throws IOException
     */
    public HashMap<String, Object> uploadFileChunk(MultipartFile file, String uploadId, int chunkIndex, int totalChunks, String key) throws IOException {
        // 获取文件输入流
        InputStream inputStream = file.getInputStream();

        // 上传文件块到 COS
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(bucketName);
        uploadPartRequest.setKey(key);
        uploadPartRequest.setUploadId(uploadId);
        uploadPartRequest.setPartNumber(chunkIndex); // COS的分块序号从1开始
        uploadPartRequest.setInputStream(inputStream); // 设置输入流
        // 设置分块大小
//        uploadPartRequest.setPartSize(file.getSize()); // 设置文件分块的大小

        // 上传分块
        UploadPartResult uploadPartResult = cosClient.uploadPart(uploadPartRequest);
        PartETag partETag = uploadPartResult.getPartETag();
        // 如果所有分块上传完成，合并文件
//        if (chunkIndex == totalChunks) {
//            mergeFile(uploadId, totalChunks, key, partETag.getETag());
//        }

        // 获取文件的下载 URL
        String downloadUrl = cosClient.getObjectUrl(bucketName, key).toString();

        // 返回上传结果
        HashMap<String, Object> map = new HashMap<>();
        map.put("key",key);
        map.put("eTag",partETag);
        map.put("downloadUrl", downloadUrl); // 加入下载地址
        return map;
    }

    /**
     * 合并分块
     * @param uploadId 唯一标识
     * @param totalChunks 总块数
     * @param key 上传文件的路径
     */
    public void mergeFile(String uploadId, int totalChunks, String key, String eTags) {
        // 创建一个合并请求
        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest();
        completeMultipartUploadRequest.setBucketName(bucketName);
        completeMultipartUploadRequest.setKey(key);
        completeMultipartUploadRequest.setUploadId(uploadId);

        // 获取所有分块的 ETag 值
        List<PartETag> partETags = new ArrayList<>();
        for (int i = 1; i <= totalChunks; i++) {
            partETags.add(new PartETag(i,eTags));
            System.err.println(new PartETag(i,eTags));
        }


        // 设置合并的部分ETags
        completeMultipartUploadRequest.setPartETags(partETags);
        // 完成合并请求
        cosClient.completeMultipartUpload(completeMultipartUploadRequest);
    }


    /**
     *
     * @param multipartFile 待上传的文件
     * @param key COS 上的文件存储路径
     * @return 上传后的文件 URL
     * @throws IOException
     */
    /**
     * 分块上传文件 (单线程版本)
     * @param multipartFile 待上传的文件
     * @param key COS 上的文件存储路径
     * @return 上传后的文件 URL
     * @throws IOException
     */
    public Map<String, Object> uploadFileInChunks(MultipartFile multipartFile, String key) throws IOException {
        // 初始化上传
        InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(bucketName, key);
        InitiateMultipartUploadResult initiateResult = cosClient.initiateMultipartUpload(initiateRequest);
        String uploadId = initiateResult.getUploadId();

        // 计算分块数量
        long partSize = 10 * 1024 * 1024; // 10MB
        long fileLength = multipartFile.getSize();
        int partCount = (int) Math.ceil((double) fileLength / partSize);

        // 用来保存每个分块的ETag
        List<PartETag> partETags = new ArrayList<>();

        // 获取文件输入流
        try (InputStream inputStream = multipartFile.getInputStream()) {
            for (int i = 0; i < partCount; i++) {
                final int partNumber = i + 1;
                final long start = i * partSize;
                final long end = Math.min(start + partSize, fileLength);

                // 跳过已上传部分
                long skipped = inputStream.skip(start);
                if (skipped != start) {
                    throw new IOException("文件读取错误，跳过不完全");
                }

                // 读取分块
                byte[] buffer = new byte[(int) (end - start)];
                int bytesRead = inputStream.read(buffer);
                if (bytesRead != buffer.length) {
                    throw new IOException("文件读取错误，读取的字节数与预期不符");
                }

                // 上传分块
                UploadPartRequest uploadPartRequest = new UploadPartRequest()
                        .withBucketName(bucketName)
                        .withKey(key)
                        .withUploadId(uploadId)
                        .withPartNumber(partNumber)
                        .withPartSize(buffer.length)
                        .withInputStream(new ByteArrayInputStream(buffer));

                UploadPartResult uploadPartResult = cosClient.uploadPart(uploadPartRequest);
                partETags.add(uploadPartResult.getPartETag());
            }

            // 完成上传
            CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(
                    bucketName, key, uploadId, partETags);
            cosClient.completeMultipartUpload(completeRequest);

        }

        HashMap<String, Object> map = new HashMap<>();
        // 返回文件的访问 URL
        map.put("url", cosClient.getObjectUrl(bucketName, key).toString());
        map.put("partETags", partETags);

        return map;
    }

    public PartETag uploadPartFile(String uploadId, String key, InputStream input, int partNumber,int batch) throws IOException {
        System.out.println("共" + batch + "块，正在进行第" + partNumber + "块");

        if (partNumber>10000) {
            throw new CosClientException("分块数量超过最大限制1000,分块数量为:"+partNumber);
        }
        COSClient cosClient = getClient();
        //实现将对象按照分块的方式上传到 COS。最多支持10000分块，每个分块大小为1MB - 5GB，最后一个分块可以小于1MB。
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setUploadId(uploadId);
        uploadPartRequest.setInputStream(input);
        uploadPartRequest.setKey(key);
        uploadPartRequest.setPartSize(input.available());
        uploadPartRequest.setBucketName(bucketName);
        uploadPartRequest.setPartNumber(partNumber);
        UploadPartResult uploadPartResult = cosClient.uploadPart(uploadPartRequest);
        PartETag partETag = new PartETag(partNumber, uploadPartResult.getETag());
        return partETag;
    }


    // 创建 COSClient 实例，这个实例用来后续调用请求
    public COSClient getClient() {
        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
        // ClientConfig 中包含了后续请求 COS 的客户端设置：
        ClientConfig clientConfig = new ClientConfig();
        // 设置 bucket 的地域，南京对应的 Region 为 ap-nanjing
        clientConfig.setRegion(new Region(region));

        // 以下的设置，是可选的：
        // 设置 socket 读取超时，默认 30s
        // clientConfig.setSocketTimeout(30*1000);
        // 设置建立连接超时，默认 30s
        // clientConfig.setConnectionTimeout(30*1000);
        // 如果需要的话，设置 http 代理，ip 以及 port
        // clientConfig.setHttpProxyIp("httpProxyIp");
        // clientConfig.setHttpProxyPort(80);
        // 生成 cos 客户端。
        return new COSClient(cred, clientConfig);
    }

    /**
     * 获取UploadId
     * @return
     */
    public  String getUploadId(String key){
        // 初始化上传
        InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(bucketName, key);
        InitiateMultipartUploadResult initiateResult = cosClient.initiateMultipartUpload(initiateRequest);
        String uploadId = initiateResult.getUploadId();
        return uploadId;
    }



    /**
     * 获取临时密钥
     * @return
     */
    public  Response getUploadIdTemporary(){
        TreeMap<String, Object> config = new TreeMap<String, Object>();
        try {
            //这里的 SecretId 和 SecretKey 代表了用于申请临时密钥的永久身份（主账号、子账号等），子账号需要具有操作存储桶的权限。
//            String secretId = System.getenv("secretId");//用户的 SecretId，建议使用子账号密钥，授权遵循最小权限指引，降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
//            String secretKey = System.getenv("secretKey");//用户的 SecretKey，建议使用子账号密钥，授权遵循最小权限指引，降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
            // 替换为您的云 api 密钥 SecretId
            config.put("secretId", secretId);
            // 替换为您的云 api 密钥 SecretKey
            config.put("secretKey", secretKey);

            // 初始化 policy
            Policy policy = new Policy();

            // 设置域名:
            // 如果您使用了腾讯云 cvm，可以设置内部域名
            //config.put("host", "sts.internal.tencentcloudapi.com");

            // 临时密钥有效时长，单位是秒，默认 1800 秒，目前主账号最长 2 小时（即 7200 秒），子账号最长 36 小时（即 129600）秒
            config.put("durationSeconds", 1800);
            // 换成您的 bucket
            config.put("bucket", bucketName);
            // 换成 bucket 所在地区
            config.put("region", region);

            // 开始构建一条 statement
            Statement statement = new Statement();
            // 声明设置的结果是允许操作
            statement.setEffect("allow");
            /**
             * 密钥的权限列表。必须在这里指定本次临时密钥所需要的权限。
             * 权限列表请参见 https://cloud.tencent.com/document/product/436/31923
             * 规则为 {project}:{interfaceName}
             * project : 产品缩写  cos相关授权为值为cos,数据万象(数据处理)相关授权值为ci
             * 授权所有接口用*表示，例如 cos:*,ci:*
             * 添加一批操作权限 :
             */
            statement.addActions(new String[]{
                    "cos:PutObject",
                    // 表单上传、小程序上传
                    "cos:PostObject",
                    // 分块上传
                    "cos:InitiateMultipartUpload",
                    "cos:ListMultipartUploads",
                    "cos:ListParts",
                    "cos:UploadPart",
                    "cos:CompleteMultipartUpload",
                    // 处理相关接口一般为数据万象产品 权限中以ci开头
                    // 创建媒体处理任务
                    "ci:CreateMediaJobs",
                    // 文件压缩
                    "ci:CreateFileProcessJobs"
            });

            /**
             * 这里改成允许的路径前缀，可以根据自己网站的用户登录态判断允许上传的具体路径
             * 资源表达式规则分对象存储(cos)和数据万象(ci)两种
             * 数据处理、审核相关接口需要授予ci资源权限
             *  cos : qcs::cos:{region}:uid/{appid}:{bucket}/{path}
             *  ci  : qcs::ci:{region}:uid/{appid}:bucket/{bucket}/{path}
             * 列举几种典型的{path}授权场景：
             * 1、允许访问所有对象："*"
             * 2、允许访问指定的对象："a/a1.txt", "b/b1.txt"
             * 3、允许访问指定前缀的对象："a*", "a/*", "b/*"
             *  如果填写了“*”，将允许用户访问所有资源；除非业务需要，否则请按照最小权限原则授予用户相应的访问权限范围。
             *
             * 示例：授权examplebucket-1250000000 bucket目录下的所有资源给cos和ci 授权两条Resource
             */
            statement.addResources(new String[]{
                    "qcs::cos:"+region+":uid/"+appId+":"+bucketName+"/*",
                    "qcs::ci:"+region+":uid/"+appId+":bucket/"+bucketName+"/*"});

            // 把一条 statement 添加到 policy
            // 可以添加多条
            policy.addStatement(statement);
            // 将 Policy 示例转化成 String，可以使用任何 json 转化方式，这里是本 SDK 自带的推荐方式
            config.put("policy", Jackson.toJsonPrettyString(policy));

            Response response = CosStsClient.getCredential(config);
            System.out.println(response.credentials.tmpSecretId);
            System.out.println(response.credentials.tmpSecretKey);
            System.out.println(response.credentials.sessionToken);
            return response;
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalArgumentException("报错了=== !");
        }
    }



    public static String generateCosKey(String ext) {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        String ymd = dateFormat.format(date);

        Random random = new Random();
        int r = random.nextInt(1000000);
        String rStr = String.format("%06d", r);

        String cosKey = String.format("file/%s/%s_%s.%s", ymd, ymd, rStr, ext != null ? ext : "");
        return cosKey;
    }

    // 获取配置信息
    public TreeMap<String,Object> getConfig(String filename){

//        String bucket = "0-1253960454";
//        String appId = "1253960454";
//        String filename = "test.jpg";
//        String region = "ap-guangzhou";
//        String secretId = "AKIDjOsDmCNwBUedQ3EouQq7C5RdmqW2eWTL";
//        String secretKey = "rWzTyVzIsKZcUCiForz40jJk7W9kAPTM";
        String proxy  = "";
        int durationSeconds = 1800;

        String[] segments = filename.split("\\.");
        String ext = segments.length > 0 ? segments[segments.length - 1] : "";

        // 临时密钥限制
        Boolean limitExt = false; // 限制上传文件后缀
        List extWhiteList = Arrays.asList("jpg", "jpeg", "png", "gif", "bmp"); // 限制的上传后缀
        Boolean limitContentType = false; // 限制上传 contentType
        Boolean limitContentLength = false; // 限制上传文件大小

        Map<String, Object> condition = new HashMap();

        // 1. 限制上传文件后缀
        if (limitExt) {
            boolean extInvalid = ext == null || !extWhiteList.contains(ext);
            if (extInvalid) {
                System.out.println("非法文件，禁止上传");
                return null;
            }
        }

        // 2. 限制上传文件 content-type
        if (limitContentType) {
            condition.put("string_like", new HashMap<String, String>() {{
                put("cos:content-type", "image/*");
            }});
        }

        // 3. 限制上传文件大小
        if (limitContentLength) {
            condition.put("numeric_less_than_equal", new HashMap<String, Long>() {{
                put("cos:content-length", 5L * 1024 * 1024);
            }});
        }
        String key = generateCosKey(ext);
        String resource = "qcs::cos:" + region + ":uid/" + appId + ':' + bucketName + '/' + key;
        List allowActions = Arrays.asList(
                // 简单上传
                "name/cos:PutObject",
                // 分块上传
                "name/cos:InitiateMultipartUpload",
                "name/cos:ListMultipartUploads",
                "name/cos:ListParts",
                "name/cos:UploadPart",
                "name/cos:CompleteMultipartUpload"
        );
        // 构建policy
        Map<String, Object> policy = new HashMap();
        policy.put("version", "2.0");
        Map<String, Object> statement = new HashMap();
        statement.put("action", allowActions);
        statement.put("effect", "allow");
        List<String> resources = Arrays.asList(
                resource
        );
        statement.put("resource", resources);
        statement.put("condition", condition);
        policy.put("statement", Arrays.asList(statement));
        // 构建config
        TreeMap <String,Object> config = new TreeMap<String, Object>();
        config.put("secretId",secretId);
        config.put("secretKey",secretKey);
        config.put("proxy",proxy);
        config.put("duration",durationSeconds);
        config.put("bucket",bucketName);
        config.put("region",region);
        config.put("key",key);
        config.put("policy",Jackson.toJsonPrettyString(policy));
        return config;
    }

    /**
     * 基本的临时密钥申请示例，适合对一个桶内的一批对象路径，统一授予一批操作权限
     */

    public void getKeyAndCredentials(String filename) {
        TreeMap config = this.getConfig( filename);
        try {
            Response response = CosStsClient.getCredential(config);
            TreeMap <String,Object> credential = new TreeMap<String, Object>();
            TreeMap <String,Object> credentials = new TreeMap<String, Object>();
            credentials.put("tmpSecretId",response.credentials.tmpSecretId);
            credentials.put("tmpSecretKey",response.credentials.tmpSecretKey);
            credentials.put("sessionToken",response.credentials.sessionToken);
            credential.put("startTime",response.startTime);
            credential.put("expiredTime",response.expiredTime);
            credential.put("requestId",response.requestId);
            credential.put("expiration",response.expiration);
            credential.put("credentials",credentials);
            credential.put("bucket",config.get("bucket"));
            credential.put("region",config.get("region"));
            credential.put("key",config.get("key"));
            System.out.println(Jackson.toJsonPrettyString(credential));
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalArgumentException("no valid secret !");
        }
    }


}
